﻿using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Monti.Controls
{
   /// <summary>
   /// Text box used for searching.
   /// Modified from: http://davidowens.wordpress.com/2009/02/18/wpf-search-text-box/
   /// </summary>
   public class SearchTextBox : TextBox
   {
      /// <summary>
      /// Static constructor.
      /// </summary>
      static SearchTextBox ()
      {
         DefaultStyleKeyProperty.OverrideMetadata (
             typeof (SearchTextBox),
             new FrameworkPropertyMetadata (typeof (SearchTextBox)));
      }

      /// <summary>
      /// Dependency Property for search command.
      /// </summary>
      public static DependencyProperty CommandProperty =
          DependencyProperty.Register (
              "Command",
              typeof (ICommand),
              typeof (SearchTextBox));

      /// <summary>
      /// Gets or Sets the command used for searching.
      /// </summary>
      public ICommand Command
      {
         get { return (ICommand)GetValue (CommandProperty); }
         set { SetValue (CommandProperty, value); }
      }

      /// <summary>
      /// Dependency Property for search command parameter.
      /// </summary>
      public static DependencyProperty CommandParameterProperty =
          DependencyProperty.Register (
              "CommandParameter",
              typeof (object),
              typeof (SearchTextBox));

      /// <summary>
      /// Gets or Sets the command parameter used for searching.
      /// </summary>
      public object CommandParameter
      {
         get { return GetValue (CommandParameterProperty); }
         set { SetValue (CommandParameterProperty, value); }
      }

      /// <summary>
      /// Dependency Property for text to display when no text is entered.
      /// </summary>
      public static DependencyProperty PlaceHolderTextProperty =
          DependencyProperty.Register (
              "PlaceHolderText",
              typeof (string),
              typeof (SearchTextBox));

      /// <summary>
      /// Gets or Sets the place holder text.
      /// </summary>
      public string PlaceHolderText
      {
         get { return (string)GetValue (PlaceHolderTextProperty); }
         set { SetValue (PlaceHolderTextProperty, value); }
      }

      /// <summary>
      /// Dependency Property for color of text to display when no text is entered.
      /// </summary>
      public static DependencyProperty PlaceHolderTextColorProperty =
          DependencyProperty.Register (
              "PlaceHolderTextColor",
              typeof (Brush),
              typeof (SearchTextBox));

      /// <summary>
      /// Gets or Sets the place holder text color.
      /// </summary>
      public Brush PlaceHolderTextColor
      {
         get { return (Brush)GetValue (PlaceHolderTextColorProperty); }
         set { SetValue (PlaceHolderTextColorProperty, value); }
      }

      /// <summary>
      /// Read only Dependency Property to check if search text has been entered.
      /// </summary>
      private static readonly DependencyPropertyKey SHasTextPropertyKey =
          DependencyProperty.RegisterReadOnly (
              "HasText",
              typeof (bool),
              typeof (SearchTextBox),
              new PropertyMetadata ());

      /// <summary>
      /// Dependency property to check if search text has been entered.
      /// </summary>
      public static DependencyProperty HasTextProperty = SHasTextPropertyKey.DependencyProperty;

      /// <summary>
      /// Gets if the search box has any entered text.
      /// </summary>
      public bool HasText
      {
         get { return (bool)GetValue (HasTextProperty); }
         private set { SetValue (SHasTextPropertyKey, value); }
      }

      /// <summary>
      /// Read only Dependency Property to check if left mouse button is down
      /// </summary>
      private static readonly DependencyPropertyKey SIsMouseLeftButtonDownPropertyKey =
          DependencyProperty.RegisterReadOnly (
              "IsMouseLeftButtonDown",
              typeof (bool),
              typeof (SearchTextBox),
              new PropertyMetadata ());

      /// <summary>
      /// Dependency property to check if left mouse button is down.
      /// </summary>
      public static DependencyProperty IsMouseLeftButtonDownProperty = SIsMouseLeftButtonDownPropertyKey.DependencyProperty;

      /// <summary>
      /// Gets if the left mouse button is pressed on the text box.
      /// </summary>
      public bool IsMouseLeftButtonDown
      {
         get { return (bool)GetValue (IsMouseLeftButtonDownProperty); }
         private set { SetValue (SIsMouseLeftButtonDownPropertyKey, value); }
      }

      /// <summary>
      /// Routed event for searching.
      /// </summary>
      public static readonly RoutedEvent SearchEvent =
          EventManager.RegisterRoutedEvent (
              "Search",
              RoutingStrategy.Bubble,
              typeof (RoutedEventHandler),
              typeof (SearchTextBox));

      /// <summary>
      /// Event fired when a search is requested.
      /// </summary>
      public event RoutedEventHandler Search
      {
         add { AddHandler (SearchEvent, value); }
         remove { RemoveHandler (SearchEvent, value); }
      }

      /// <summary>
      /// Occurs when the control gets a template applied.
      /// </summary>
      public override void OnApplyTemplate ()
      {
         base.OnApplyTemplate ();

         var iconBorder = GetTemplateChild ("PART_SearchIconBorder") as Border;
         if (iconBorder != null)
         {
            iconBorder.MouseLeftButtonDown += IconBorderMouseLeftButtonDown;
            iconBorder.MouseLeftButtonUp += IconBorderMouseLeftButtonUp;
            iconBorder.MouseLeave += IconBorderMouseLeave;
         }
         var clearIcon = GetTemplateChild ("ClearIcon") as Image;
         if (clearIcon != null)
            clearIcon.MouseUp += ClearText;
         this.KeyDown += KeyPressed;
      }

      /// <summary>
      /// Occurs when a key is pressed in the text box.
      /// </summary>
      /// <param name="sender">This text box.</param>
      /// <param name="e">KeyEventArgs</param>
      private void KeyPressed (object sender, KeyEventArgs e)
      {
         if (e.Key == Key.Enter && !e.Handled)
         {
            e.Handled = true;
            this.RaiseSearchEvent ();
         }
      }

      /// <summary>
      /// Occurs when the clear button is pressed.
      /// </summary>
      /// <param name="sender">ClearIcon</param>
      /// <param name="e">MouseButtonEventArgs</param>
      private void ClearText (object sender, MouseButtonEventArgs e)
      {
         this.Clear ();
      }

      /// <summary>
      /// Occurs when the text in the text box changes.
      /// </summary>
      /// <param name="e">TextChangedEventArgs</param>
      protected override void OnTextChanged (TextChangedEventArgs e)
      {
         base.OnTextChanged (e);
         HasText = Text.Trim ().Length != 0;
      }

      /// <summary>
      /// Occurs when the left mouse button is pressed.
      /// </summary>
      /// <param name="obj">PART_SearchIconBorder (Border)</param>
      /// <param name="e">MouseButtonEventArgs</param>
      private void IconBorderMouseLeftButtonDown (object obj, MouseButtonEventArgs e)
      {
         IsMouseLeftButtonDown = true;
      }

      /// <summary>
      /// Occurs when the left mouse button is released.
      /// </summary>
      /// <param name="obj">PART_SearchIconBorder (Border)</param>
      /// <param name="e">MouseButtonEventArgs</param>
      private void IconBorderMouseLeftButtonUp (object obj, MouseButtonEventArgs e)
      {
         if (!IsMouseLeftButtonDown) return;
         RaiseSearchEvent ();

         IsMouseLeftButtonDown = false;
      }

      /// <summary>
      /// Occurs when the mouse leaves the control.
      /// </summary>
      /// <param name="obj">PART_SearchIconBorder (Border)</param>
      /// <param name="e">MouseEventArgs</param>
      private void IconBorderMouseLeave (object obj, MouseEventArgs e)
      {
         IsMouseLeftButtonDown = false;
      }

      /// <summary>
      /// Raises the search event.
      /// </summary>
      private void RaiseSearchEvent ()
      {
         if (HasText)
         {
            var args = new RoutedEventArgs (SearchEvent);
            RaiseEvent (args);
            var command = this.Command;
            if (command != null && command.CanExecute (this.CommandParameter))
               command.Execute (this.CommandParameter);
         }
      }
   }
}
